/* Copyright (C) 2010 Luca Piccinelli
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* 
 * File:   pipeline_data_structures.h
 * Author: lpiccinelli
 *
 * Created on 3 agosto 2010, 20.55
 */

#ifndef PIPELINE_DATA_STRUCTURES_H
#define	PIPELINE_DATA_STRUCTURES_H

#include <set>
#include <map>
#include <list>
#include <vector>
#include <exception>

#include "prj_definitions.h"
#include "pipeline/pipeline_io.h"
#include "generators/generators.h"
#include "formatters/formatters.h"
#include "common/utility_data_structures.h"
#include "exceptions/pipeline_exceptions.h"

namespace NAMESPACE{

typedef IOElement* pipe_input_t;
typedef IOElement* pipe_output_t;
typedef IOElement* pipe_criteria_t;

// ****** interface of a computation unit of a pipeline step *******************
class PipeStepComputation{
public:
    typedef pipe_input_t input_t;
    typedef pipe_output_t output_t;
    typedef pipe_criteria_t criteria_t;

    virtual ~PipeStepComputation(){};
    virtual bool doOperation(const std::list<input_t>& inputs,
                             const std::list<criteria_t>& operationCriteria,
                             std::list<output_t>& outputs) = 0;

    virtual const std::vector<IOKey>& getInputKeysList() const = 0;
    virtual const std::vector<IOKey>& getCriteriaKeysList() const = 0;
    virtual const std::vector<IOKey>& getOutputKeysList() const = 0;
};
// -----------------------------------------------------------------------------

// ****** Abstract operation definition ****************************************
template<typename OP>
class AbstractOperation : public PipeStepComputation{
public:
    typedef OP operation_t;

protected:
    size_t min_input_num;
    size_t min_criteria_num;
    size_t min_output_num;

    operation_t* operation;
    std::map<IOKey, input_t>* input_mapping;
    std::map<IOKey, criteria_t>* criteria_mapping;
    std::map<IOKey, output_t>* output_mapping;

    std::vector<IOKey>* input_keys;
    std::vector<IOKey>* criteria_keys;
    std::vector<IOKey>* output_keys;

    void prepareIO(const std::list<input_t>& inputs,
                   const std::list<criteria_t>& operationCriteria,
                   const std::list<output_t>& outputs) throw(bad_io_mapping){

        // check for the correct number of inputs, criteria and outputs
        if(inputs.size() < min_input_num)  throw bad_io_mapping("too few inputs");
        if(operationCriteria.size() < min_criteria_num)  throw bad_io_mapping("too few criteria");
        if(outputs.size() < min_output_num)  throw bad_io_mapping("too few outputs");

        // perform the mapping for input, criteria and output
        typename std::list<input_t>::const_iterator in_it = inputs.begin();
        typename std::list<criteria_t>::const_iterator crit_it = operationCriteria.begin();
        typename std::list<output_t>::const_iterator out_it = outputs.begin();

        for(; in_it != inputs.end(); in_it++) (*input_mapping)[(*in_it)->getIOKey()] = *in_it;
        for(; crit_it != operationCriteria.end(); crit_it++) (*criteria_mapping)[(*crit_it)->getIOKey()] = *crit_it;
        for(; out_it != outputs.end(); out_it++) (*output_mapping)[(*out_it)->getIOKey()] = *out_it;

        // check if the passed io element are those requested by the specific Operation
        std::string error_s("Missing I/O element for key ");

        // NOTE: check on IOkey matching performs also a data type check
        IOKey k;
        for(unsigned int i = 0; i < min_input_num; i++){
            k = input_keys->at(i);
            if(input_mapping->find(k) == input_mapping->end())
                throw bad_io_mapping(error_s + k.get_key());
        }

        for(unsigned int i = 0; i < min_criteria_num; i++){
            k = criteria_keys->at(i);
            if(criteria_mapping->find(k) == criteria_mapping->end())
                throw bad_io_mapping(error_s + k.get_key());
        }
        
        for(unsigned int i = 0; i < min_output_num; i++){
            k = output_keys->at(i);
            if(output_mapping->find(k) == output_mapping->end())
                throw bad_io_mapping(error_s + k.get_key());
        }
    }

public:
    virtual ~AbstractOperation(){
        if(operation) delete operation;
        if(input_mapping) delete input_mapping;
        if(criteria_mapping) delete criteria_mapping;
        if(output_mapping) delete output_mapping;

        if(input_keys) delete input_keys;
        if(criteria_keys) delete criteria_keys;
        if(output_keys) delete output_keys;
    }
    AbstractOperation() : PipeStepComputation(),
                          min_input_num(0),
                          min_criteria_num(0),
                          min_output_num(0),
                          operation(new OP()),
                          input_mapping(new std::map<IOKey, input_t>()),
                          criteria_mapping(new std::map<IOKey, criteria_t>()),
                          output_mapping(new std::map<IOKey, output_t>()),
                          input_keys(new std::vector<IOKey>()),
                          criteria_keys(new std::vector<IOKey>()),
                          output_keys(new std::vector<IOKey>()) {}
    
    AbstractOperation(const AbstractOperation<operation_t>& op) :
                            PipeStepComputation(op),
                            min_input_num(op.min_input_num),
                            min_criteria_num(op.min_criteria_num),
                            min_output_num(op.min_output_num),
                            operation(op.operation ? new OP(*(op.operation)) : 0),
                            input_mapping(op.input_mapping ? new std::map<IOKey, input_t>(*(op.input_mapping)) : 0),
                            criteria_mapping(op.criteria_mapping ? new std::map<IOKey, input_t>(*(op.criteria_mapping)) : 0),
                            output_mapping(op.output_mapping ? new std::map<IOKey, output_t>(*(op.output_mapping)) : 0),
                            input_keys(op.input_keys ? new std::vector<IOKey>(*(op.input_keys)) : 0),
                            criteria_keys(op.output_keys ? new std::vector<IOKey>(*(op.criteria_keys)) : 0),
                            output_keys(op.criteria_keys ? new std::vector<IOKey>(*(op.output_keys)) : 0){}

    AbstractOperation<operation_t>& operator=(const AbstractOperation<operation_t>& op){
        if(this == &op) return *this;

        operation_t* tmp_op = operation;
        
        std::map<IOKey, input_t>* tmp_im = input_mapping;
        std::map<IOKey, criteria_t>* tmp_cm = criteria_mapping;
        std::map<IOKey, output_t>* tmp_om = output_mapping;

        std::vector<IOKey>* tmp_ik = input_keys;
        std::vector<IOKey>* tmp_ck = criteria_keys;
        std::vector<IOKey>* tmp_ok = output_keys;

        operation = 0;

        input_mapping = 0;
        criteria_mapping = 0;
        output_mapping = 0;

        input_keys = 0;
        criteria_keys = 0;
        output_keys = 0;

        try{
            if(op.operation) operation = new OP(*(op.operation));

            if(op.input_mapping) input_mapping = new std::map<IOKey, input_t>(*(op.input_mapping));
            if(op.criteria_mapping) criteria_mapping = new std::map<IOKey, criteria_t>(*(op.criteria_mapping));
            if(op.output_mapping) output_mapping = new std::map<IOKey, output_t>(*(op.output_mapping));

            if(op.input_keys) input_keys = new std::vector<IOKey>(*(op.input_keys));
            if(op.criteria_keys) criteria_keys = new std::vector<IOKey>(*(op.criteria_keys));
            if(op.output_keys) output_keys = new std::vector<IOKey>(*(op.output_keys));
        }catch(...){
            if(operation) delete operation;
            operation = tmp_op;

            if(input_mapping) delete input_mapping;
            input_mapping = tmp_im;

            if(criteria_mapping) delete criteria_mapping;
            criteria_mapping = tmp_cm;

            if(output_mapping) delete output_mapping;
            output_mapping = tmp_om;

            if(input_keys) delete input_keys;
            input_keys = tmp_ik;

            if(criteria_keys) delete criteria_keys;
            criteria_keys = tmp_ck;

            if(output_keys) delete output_keys;
            output_keys = tmp_ok;

            throw;
        }

        min_input_num = op.min_input_num;
        min_criteria_num = op.min_criteria_num;
        min_output_num = op.min_output_num;
        
        if(tmp_op) delete tmp_op;
        
        if(tmp_im) delete tmp_im;
        if(tmp_cm) delete tmp_cm;
        if(tmp_om) delete tmp_om;

        if(tmp_ik) delete tmp_ik;
        if(tmp_ck) delete tmp_ck;
        if(tmp_ok) delete tmp_ok;

        return *this;
    }

    virtual bool doOperation(const std::list<input_t>& inputs,
                             const std::list<criteria_t>& operationCriteria,
                             std::list<output_t>& outputs) = 0;

    const std::vector<IOKey>& getInputKeysList() const {
        return *input_keys;
    }
    const std::vector<IOKey>& getCriteriaKeysList() const {
        return *criteria_keys;
    }
    const std::vector<IOKey>& getOutputKeysList() const {
        return *output_keys;
    }

   bool operator==(const AbstractOperation<operation_t>& op) const{
       if( typeid(op) != (typeid(*this)) ) return false;

       if(min_input_num    != op.min_input_num    ||
          min_criteria_num != op.min_criteria_num ||
          min_output_num   != op.min_output_num) return false;

       if(*input_keys    != *(op.input_keys)    ||
          *criteria_keys != *(op.criteria_keys) ||
          *output_keys   != *(op.output_keys)    ) return false;

       if(*input_mapping    != *(op.input_mapping)    ||
          *criteria_mapping != *(op.criteria_mapping) ||
          *output_mapping   != *(op.output_mapping)    ) return false;

       return true;
   }
};
// -----------------------------------------------------------------------------

// ****** PipeStep definition **************************************************
class PipeStep{

public:
    typedef pipe_input_t input_t;
    typedef pipe_output_t output_t;
    typedef pipe_criteria_t criteria_t;

private:
    std::map<IOKey, input_t>* inputs;
    std::map<IOKey, criteria_t>* criteria;
    std::map<IOKey, output_t>* outputs;

    std::list<Generator*>* input_generators;
    std::list<Generator*>* criteria_generators;
    std::list<Generator*>* output_generators;

    std::list<Formatter*>* output_formatters;

    PipeStepComputation* computation;

public:
    ~PipeStep();
    PipeStep();
    PipeStep(const PipeStep& ps);
    PipeStep& operator=(const PipeStep& ps);

    virtual bool execute() throw(bad_computation, bad_io_mapping, gestface_exception);

    PipeStep& add_input(input_t _input);
    PipeStep& add_criteria(input_t _criteria);
    PipeStep& add_output(input_t _output);

    PipeStep& add_input_generator(Generator* gen);
    PipeStep& add_criteria_generator(Generator* gen);
    PipeStep& add_output_generator(Generator* gen);

    PipeStep& add_output_formatter(Formatter* formatter);

    PipeStep& set_computation(PipeStepComputation* _computation);
};
// -----------------------------------------------------------------------------

// ****** Pipeline definition **************************************************
class Pipeline : public PipeStepComputation{
public:
    typedef pipe_input_t input_t;
    typedef pipe_output_t output_t;
    typedef pipe_criteria_t criteria_t;

private:
    std::vector<PipeStep*>* pipe_steps;

public:
    ~Pipeline();
    Pipeline();
    Pipeline(const Pipeline& p);
    Pipeline& operator=(const Pipeline& p);

    bool doOperation(const std::list<input_t>& inputs,
                     const std::list<criteria_t>& operationCriteria,
                     std::list<output_t>& outputs) throw(bad_io_mapping);

    const std::vector<IOKey>& getInputKeysList() const;
    const std::vector<IOKey>& getCriteriaKeysList() const;
    const std::vector<IOKey>& getOutputKeysList() const;

    Pipeline& add_pipe_step(PipeStep* pipe_step);
};
// -----------------------------------------------------------------------------

}
#endif	/* PIPELINE_DATA_STRUCTURES_H */
